#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <unordered_map>
#include <string>
#include <map>
#include <algorithm>
#include <cctype>
#include <sstream>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include "Utils.hpp"
#include "Log.hpp"

const std::string SEP = ": ";
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500
#define wwwroot "wwwroot"
#define HOME_PAGE "index.html"
#define PAGE_404 "404.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"

static std::string Code2Desc(int code) {
    std::string desc;
    switch (code)
    {
    case 200:
        /* code */
        desc = "OK";
        break;
    case 404:
        desc = "Not Found";
        break;
    default:
        break;
    }
    return desc;
}

static std::string Suffix2Desc(const std::string &suffix) {
    static std::map<std::string, std::string> suffix2desc = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "text/xml"}
    };
    std::map<std::string, std::string>::iterator it = suffix2desc.find(suffix);
    if (it != suffix2desc.end()) {
        return it->second;
    }
    // 默认返回content-type是"html/text"
    return "html/text";
}

class HttpRequest {
    public:
        std::string request_line;
        std::vector<std::string> request_header;
        std::string blank;
        std::string request_body;

        int content_length = 0;

        // 下面是请求头解析的结果
        std::string method;
        std::string uri;
        std::string version;
        std::map<std::string, std::string> header_map;
        std::string path; // uri切分成路径
        std::string query_parameter;  //uri切分成携带的参数

        bool cgi = false;   // 判断请求是否需要使用cgi的方式来处理
};

class HttpResponse {
    public:
        std::string response_line;
        std::vector<std::string> response_header;
        std::string blank = LINE_END;
        std::string response_body;
        int status_code = OK;
        int fd = -1;
        int size = 0;
        std::string suffix;
};

class EndPoint {
    private:
        bool RecvHttpRequestLine() {
            std::string &line = http_request.request_line;
            if (Utils::ReadLine(sock, line) > 0) {
                line.resize(line.size() - 1);
                LOG(INFO, http_request.request_line);
            } else {
                stop = true;
            }
            return stop;
        }
        bool RecvHttpRequestHeader() {
            std::string line;
            while (true) {
                line.clear();
                if (Utils::ReadLine(sock, line) <= 0) {
                    stop = true;
                    break;
                }
                if (line == "\n") {
                    http_request.blank = '\n';
                    break;
                }
                line.resize(line.size() - 1);
                http_request.request_header.push_back(line);
                LOG(INFO, line);
            }
            return stop;
        }
        void ParseHttpRequestLine() {
            std::string &line = http_request.request_line;
            // stringstream可以根据空格进行一行字符串的分割
            std::stringstream ss(line);
            ss >> http_request.method >> http_request.uri >> http_request.version;
            std::string &method = http_request.method;
            std::transform(method.begin(), method.end(), method.begin(), ::toupper);
            std::cout << "*******************************" << std::endl;
            LOG(INFO, http_request.method);
            LOG(INFO, http_request.uri);
            LOG(INFO, http_request.version);            
            std::cout << "*******************************" << std::endl;;
        }
        void ParseHttpRequestHeader() {
            std::string key;
            std::string value;
            for (auto &line : http_request.request_header) {
                if(Utils::CutString(line, key, value, SEP)) {
                    http_request.header_map.insert({key, value});
                }
            }
            for (auto &it : http_request.header_map) {
                std::cout << "debug  " << it.first << ":" << it.second << std::endl;
            }
        }

        // 只有post请求有body正文，这里判断是否需要对正文进行读取解析
        bool IsNeedRecvHttpRequestBody() {
            if (http_request.method == "POST") {
                auto it = http_request.header_map.find("Content-Length");
                if (it != http_request.header_map.end()) {
                    http_request.content_length = atoi(it->second.c_str());
                    return true;
                }
            }
            return false;
        }
        bool RecvHttpRequestBody() {
            if (IsNeedRecvHttpRequestBody()) {
                std::string &body = http_request.request_body;
                int content_length = http_request.content_length;
                while (content_length) {
                    char ch = 0;
                    int s = recv(sock, &ch, 1, 0);
                    if (s > 0) {
                        body.push_back(ch);
                        content_length--;
                    } else {
                        stop = true;
                        break;
                    }
                }
                // std::cout << "debug RecvHttpRequestBody -- body: " << body << std::endl;
            }
            return stop;
        }
      
        int ProcessCgi() {
            int code;
            std::string &parameter_string = http_request.query_parameter;  // GET
            std::string &body_text = http_request.request_body;  // POST
            std::string &response_body = http_response.response_body;
            const std::string &method = http_request.method;
            int content_length = http_request.content_length;
            std::string &bin = http_request.path;
            std::string query_string_env;  // 用来作为环境变量传参给exec*后的程序,GET方法使用环境变量方式传递参数
            std::string methed_env;  // 不管是POST还是GET，都要通过环境传参让exec*后的程序知道发起请求的是什么方法
            int input[2];
            int output[2];
            if (pipe(input) < 0) {
                LOG(ERROR, "pipe input error");
                code = SERVER_ERROR;
                return code;
            }
            if (pipe(output) < 0) {
                LOG(ERROR, "pipe output error");
                code = SERVER_ERROR;
                return code;
            }
            pid_t pid = fork();
            if (pid == 0) { //child 进程替换      父进程向子进程传数据、父进程向子进程拿回处理后的数据 -----> 进程通信
                // std::cout << "child process body: " << body_text << std::endl; // 子进程也可以拿到数据，之所以要父进程传body数据，是因为子进程被程序替换后，数据就没了
                close(input[0]);
                close(output[1]);
                // 设置何种请求方法的环境变量
                methed_env = "METHOD=";
                methed_env += method;
                putenv((char *)methed_env.c_str());
                // 子进程判断如果是GET方法，就用环境变量的方式将请求url中跟着的参数传递给exec*后的程序
                if (method == "GET") {
                    query_string_env = "QUERY_STRING=";
                    query_string_env += parameter_string;
                    putenv((char *)query_string_env.c_str());  // 通过putenv设置环境变量
                } else if (method == "POST") {
                    // 将body长度设置为环境变量，便于cgi程序知道取多少字符
                    std::string content_length_env = "CONTENT_LENGTH=";
                    content_length_env += std::to_string(content_length);
                    putenv((char *)content_length_env.c_str());
                } else {

                }
                // 因为程序替换之后，原进程数据代码都会被替换，因此input/output里打开的文件描述符不知道是多少了，
                // 但是程序替换并不会替换进程内核数据结构，因此替换后的进程所打开的文件描述符表跟之前是一样的。
                // 解决方法：exec之前重定向一下，当作一个约定：0号描述符读，1号描述符写
                std::cout << "bin: " << bin << std::endl;
                dup2(input[1], 1);
                dup2(output[0], 0);
                /* exec* */
                // execl("/usr/bin/ls", "ls", "-l", "-a", NULL)
                // 因为执行命令 ls 等价于 /usr/bin/ls(写全了)
                execl(bin.c_str(), bin.c_str(), nullptr);
                // 一旦执行到exit就说明程序替换失败，所有退出码是1
                exit(1);  
            } else if (pid < 0) { // 失败
                LOG(ERROR, "fork error!");
                code = 404;
                return code;
            } else { // father 进程等待
                close(input[1]);
                close(output[0]);
                if (method == "POST") {
                    ssize_t size = 0; // 记录write的返回值
                    ssize_t total = 0; // 记录当前已经一共写入多少字节
                    const char *start = body_text.c_str();
                    // 你怎么能保证一次write就全部发完呢？所以要循环发送
                    while (true) {
                        if (total < content_length) {
                            size = write(output[1], start + total, body_text.size() - total);
                            if (size <= 0) {
                                break;
                            }
                            total += size;
                        } else {
                            break;
                        }
                    }
                }
                //cgi 处理完成后，会把数据写进管道里，父进程从管道里读取子进程写的数据
                char ch;
                while (true) {
                    int s = read(input[0], &ch, 1);
                    if (s > 0) {
                        response_body.push_back(ch);
                    } else {
                        break;
                    }
                }
                // std::cout << "response body debug: " << response_body << std::endl;
                int status;
                waitpid(pid, &status, 0);
                // 如果子进程正常退出，WIFEXITED返回true
                if (WIFEXITED(status)) {
                    // 正常退出，状态码为0
                    if (WEXITSTATUS(status) == 0) {
                        code = OK;
                    } else {
                        code = BAD_REQUEST;
                    }
                } else {
                    code = SERVER_ERROR;
                }
                // 网络服务器里文件描述符也是很昂贵的资源，用完需要关闭
                close(input[0]);
                close(output[1]);
            }
            return code;
        }

        int ProcessNoneCgi() {
            // 先打开文件，留着在send中调用 SendHttpResponse 发送正文
            http_response.fd = open(http_request.path.c_str(), O_RDONLY);
            // std::cout << "xupeng9 " << std::endl;
            if (http_response.fd > 0) {
                // std::cout << "xupeng10 " << std::endl;
                return OK;
            }
            return NOT_FOUND;
        }   
    
        void HandlerErrorFound(const std::string &page) {
            http_request.cgi = false;  // 不管是cgi还是非cgi处理，只要出错了，都返回错误网页；因此出错后需要调整一下cgi，使之在send时，走sendfile发送打开的网页
            std::string path = "wwwroot/";
            path += page;
            http_response.fd = open(path.c_str(), O_RDONLY);
            if (http_response.fd > 0) {
                struct stat st;
                stat(path.c_str(), &st);
                std::string line = "Content-Type: text/html";
                line += LINE_END;
                http_response.response_header.push_back(line);
                line = "Content-Length: ";
                line += std::to_string(st.st_size);
                line += LINE_END;
                http_response.response_header.push_back(line);
                http_response.size = st.st_size;  // 出错了更新一下需要返回的文件的大小
            }
        }

        void HandlerOKFound() {
            // TODO 构建response_header
            std::string header_line = "Content-Type: ";
            // std::cout << "Suffix: " << http_response.suffix << std::endl;
            header_line += Suffix2Desc(http_response.suffix);
            header_line += LINE_END;
            http_response.response_header.push_back(header_line);
            header_line = "Content-Length: ";
            if (http_request.cgi == true) {
                // std::cout << "xupeng12 " << std::endl;
                // 如果需要cgi处理，那么返回的内容不是静态网页，是cgi返回的处理结果，存放在response_body中
                header_line += std::to_string(http_response.response_body.size());
            } else {
                //  std::cout << "xupeng14 " << std::endl;       
                // 如果不需要cgi处理，则返回的是静态网页，大小在stat检测这个网页时存放在http_response的size中
                header_line += std::to_string(http_response.size);
            }
            header_line += LINE_END;
            http_response.response_header.push_back(header_line);
        }

        void BuildHttpResponseHelper() {
            std::string &response_line = http_response.response_line;
            auto &code = http_response.status_code;
            response_line += HTTP_VERSION;
            response_line += " ";
            response_line += std::to_string(code);
            response_line += " ";
            response_line += Code2Desc(code);
            response_line += LINE_END;
            switch (code)
            {
            case OK:
                // std::cout << "OK" << std::endl;
                HandlerOKFound();
                break;
            case NOT_FOUND:
                HandlerErrorFound(PAGE_404);
                break;
            case BAD_REQUEST:
                HandlerErrorFound(PAGE_404);
                break;
            case SERVER_ERROR:
                HandlerErrorFound(PAGE_404);
                break;
            default:
                break;
            }
        }
    public:
        EndPoint(int _sock)
            :sock(_sock)
            // ,http_request(nullptr)
            // ,http_response(nullptr)
        {}
       
        bool IsStop() {
            return stop;
        }
     
        void ReadHttpRequest() {
            // // 读取请求行
            // RecvHttpRequestLine();
            // // 读取报头
            // RecvHttpRequestHeader();
            if ((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader())) {
                // 解析请求行，分别得到请求方法、uri、版本号
                ParseHttpRequestLine();
                // 解析请求报头，得到各个字段对应的key：val
                ParseHttpRequestHeader();
                // 分析若有正文，则读取正文
                RecvHttpRequestBody();
            }
        }

        void BuildHttpResponse() {
            // 请求已经全部读完，可以开始构建响应了
            auto &code = http_response.status_code;
            std::string tmp_path;  // get下非cgi记录原本的uri，get下cgi记录分离参数后的uri，post下记录原本uri
            ssize_t suffix_pos = -1; // 用来记录path中的"."的位置，用来提取后缀
            // std::cout << "xupeng1 " << std::endl;
            if (http_request.method != "GET" && http_request.method != "POST") {
                LOG(WARNING, "method is not valid and not found");
                code = BAD_REQUEST;
                goto END;
            } 
            if (http_request.method == "GET") {
                auto it = http_request.uri.find("?");
                // std::cout << "xupeng2 " << std::endl;
                if (it != std::string::npos) {
                    // 有参数
                    // std::cout << "xupeng3 " << std::endl;
                    Utils::CutString(http_request.uri, http_request.path, http_request.query_parameter, "?");
                    // std::cout << http_request.uri << "   " << http_request.path << http_request.query_parameter << std::endl;
                    // 有参数，需要处理数据，需要gci
                    http_request.cgi = true;
                } else {
                    // 没有参数
                    // std::cout << "xupeng4 " << std::endl;
                    http_request.path = http_request.uri;
                    http_request.query_parameter = "";
                }
            } else if (http_request.method == "POST") {
                // post方法需要处理数据，因此需要gci 
                http_request.path = http_request.uri;
                http_request.cgi = true;
            } else {
                // DO Nothing
            }
            // 将用户传上来的path，链接到我们内部的真实资源存在的path
            // std::cout << "xupeng5 " << std::endl;
            tmp_path = http_request.path;
            http_request.path = wwwroot;
            http_request.path += tmp_path;
            // 最后一个字符为"/"表示用户没有传入任何路径，此时返回默认首页
            if (http_request.path[http_request.path.size() - 1] == '/') {
                // std::cout << "xupeng6 " << std::endl;
                http_request.path += HOME_PAGE;
            }
            // std::cout << "xupeng7 " << std::endl;
            // std::cout << http_request.path << std::endl;
            // stat 判断用户传进来的path的文件是否存在
            struct stat st;  // stat的一个输入输出参数
            if (stat(http_request.path.c_str(), &st) == 0) {
                // path存在
                if (S_ISDIR(st.st_mode)) {
                    // 如果path是一个dir而不是文件，默认返回改dir下的index.html
                    http_request.path += "/";
                    http_request.path += HOME_PAGE;
                    stat(http_request.path.c_str(), &st);
                }
                if ((st.st_mode & S_IXGRP) || (st.st_mode & S_IXUSR) || (st.st_mode & S_IXOTH)) {
                    // 用户传入的path代表的文件是一个可执行文件
                    http_request.cgi = true;  // 如果请求的是一个可执行程序，则也需要CGI机制
                }
                // 走到这，请求的资源肯定存在，记录一下资源的大小
                http_response.size = st.st_size;
            } else {
                // path不存在
                std::string info = http_request.path;
                info += " Not Found";
                LOG(WARNING, info);
                code = NOT_FOUND;
                goto END;
            }
            // std::cout << "xupeng7 " << std::endl;
            // 请求的资源肯定存在
            // 记录一下返回响应的内容的类型
            suffix_pos = http_request.path.rfind(".");
            if (suffix_pos == std::string::npos) {
                // 没找到.xxx结尾的后缀，则默认是.html
                http_response.suffix = ".html";
            } else {
                // 有.xxx形式的后缀，来提取后缀
                http_response.suffix = http_request.path.substr(suffix_pos);
            }

            if (true == http_request.cgi) {
                // 需要使用CGI，执行目标程序，拿到结果放在 http_response.response_body
                code = ProcessCgi();
            } else {
                // 不需要使用CGI
                // std::cout << "xupeng8 " << std::endl;
                http_response.status_code = OK;
                // 需要返回接收code，是因为防止打开文件不成功，虽然应该大概率会成功，因为前面已经判断过这个文件肯定存在
                code = ProcessNoneCgi();  // 返回静态网页，只需要打开这个网页记录fd即可
            }
            END: 
                // 如果请求的资源不存在，或者文件打开不成功
                // std::cout << "xupeng11 " << std::endl;
                 BuildHttpResponseHelper();
                return;         
        }

        void SendHttpResponse() { 
            // 先把响应行添加进tcp发送缓冲区发送
            send(sock, http_response.response_line.c_str(), http_response.response_line.size(), 0);
            // 再把响应头添加进发送缓冲区发送
            for (auto it : http_response.response_header) {
                send(sock, it.c_str(), it.size(), 0);
            }
            // 再发送空行
            send(sock, http_response.blank.c_str(), http_response.blank.size(), 0);
            // 对cgi处理的和非cgi处理的，需要返回的方式不同
            if (http_request.cgi) {
                // cgi处理不出错，则发送response_body中的内容
                // size_t size = 0;
                // size_t total = 0;
                // const char *start = http_response.response_body.c_str();
                // while (true) {
                //     if (total < http_response.response_body.size()) {
                //         size = send(sock, start + total, http_response.response_body.size() - total, 0);
                //         if (size > 0) {
                //             total += size;
                //         } else {
                //             break;
                //         }
                //     } else {
                //         break;
                //     }
                // }
                // std::cout << "xupeng15 " << std::endl;
                auto &response_body = http_response.response_body;
                size_t size = 0;
                size_t total = 0;
                const char *start = response_body.c_str();
                while( total < response_body.size() && (size = send(sock, start + total, response_body.size() - total, 0)) > 0)
                {
                    total += size;
                }
            } else {
                // 非cgi返回静态网页或则cgi处理出错，用sendfile发送正文
                sendfile(sock, http_response.fd, nullptr, http_response.size);
                close(http_response.fd);
            }
        }

        ~EndPoint() {
            close(sock);
        }
    private:
        int sock;
        HttpRequest http_request;
        HttpResponse http_response;
        bool stop = false;
};


 
// #define DEBUG 1
class Entrance {
    public:
        static void* HandlerRequest(void *args) {
            LOG(INFO, "Hander Request Begin");
            int sock = *(int*)args; 
            delete (int*)args;;
        #ifdef DEBUG
            char buffer[1024];
            int s = read(sock, buffer, sizeof(buffer) - 1);
            int s = recv(sock, buffer, sizeof(buffer) - 1, 0);
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        #else 
            EndPoint *ep = new EndPoint(sock);
            ep->ReadHttpRequest();
            if (!ep->IsStop()) {
                LOG(INFO, "Recv No Error, Build and Send will Begin!");
                ep->BuildHttpResponse();
                ep->SendHttpResponse();
            } else {
                LOG(WARNING, "Recv have Error!");
            }
            delete ep;
        #endif
            // close(sock);      在EndPoint里做了close
            LOG(INFO, "Hander Request End");
            return nullptr;
        }
};
